package dao

import (
	"context"
	"gorm.io/gorm"
	"time"
)

type JobDAO interface {
	Preempt(ctx context.Context) (JobModel, error)
	UpdateChangeTime(ctx context.Context, id int64) error
	GetJob(ctx context.Context, id int64) (JobModel, error)
	Release(ctx context.Context, id int64) error
	UpdateNextTime(ctx context.Context, id int64, time time.Time) error
}

/**
考虑一下问题
1. 那些任务可以抢？
处于待调度状态且下次执行时间已经到达或已过的任务。这些任务已经准备就绪，可以被调度执行。(status = ? AND next_exec_tim < ?)
2. 那些任务已经被占用了
那些其他事务已经抢占并正在执行的任务。这些任务在被其他事务锁定期间不能被抢占，直到锁定释放后才能再次被抢占。
3. 哪些任务永远不会被调度
已经完成或被取消的任务，或者是下次执行时间在未来某个时间点的任务(status = ? AND next_exec_tim > ?)。这些任务不符合被抢占的条件，因此永远不会被调度执行。

*/

type gormJobDAO struct {
	db *gorm.DB
}

func NewJobDAO(db *gorm.DB) JobDAO {
	return &gormJobDAO{db: db}
}

func (g *gormJobDAO) Release(ctx context.Context, id int64) error {
	now := time.Now()
	return g.db.WithContext(ctx).Model(&JobModel{}).Where("id = ?", id).Updates(map[string]any{
		"updated_at": now,
		"status":     JobStatusWaiting,
	}).Error
}

func (g *gormJobDAO) UpdateNextTime(ctx context.Context, id int64, t time.Time) error {
	now := time.Now()
	return g.db.WithContext(ctx).Model(&JobModel{}).Where("id = ?", id).Updates(map[string]any{
		"updated_at":     now,
		"next_exec_time": t,
	}).Error
}

func (g *gormJobDAO) Preempt(ctx context.Context) (JobModel, error) {
	for {
		now := time.Now()
		var res JobModel
		//执行时间已经过期，说明正在等待被抢占
		err := g.db.WithContext(ctx).Where("status = ? AND next_exec_tim < ?", JobStatusWaiting, now).First(&res).Error
		if err != nil {
			return JobModel{}, err
		}
		// 分布式任务调度系统,随机就是减少并发冲突，就是where筛选调价添加offset和limit。，批量减少i/o 随机降低并发冲突
		// 1. 一次拉一批 2. 随机从某一条开始，从后开始抢占  3. 随机偏移量+取模/第一轮没有查到，偏移量归零

		// 这里是乐观锁实现 CAS  Compare And Swap  常见的面试装逼 => 用乐观锁取代 for update   forupdate性能差，容易引起死锁问题。 性能优化的套路
		//乐观锁是一种并发控制机制，它假设并发访问的冲突很少发生，因此不会立即对共享资源进行加锁，而是在更新操作时检查资源的版本号或标识，以确定在操作期间是否有其他并发操作对资源进行了修改。常见的乐观锁实现方式是使用比较并交换（Compare and Swap，CAS）操作。
		//CAS 是一种原子操作，它会比较内存中的值与预期的值，如果相等，则将内存位置的值更新为新值。

		//悲观锁。。在传统的数据库事务中，悲观锁（如使用 for update）会在读取数据时对数据进行锁定，以确保其他事务无法修改这些数据，直到当前事务结束
		ans := g.db.WithContext(ctx).Model(&JobModel{}).Where("id = ? AND version = ?", res.ID, res.Version).Updates(map[string]any{
			//乐观锁的操作
			//读取资源的当前版本号或标识。
			//执行更新操作前，再次读取资源的版本号或标识，确保资源未被修改。版本号加1
			//如果资源未被修改，执行更新操作，并将新的版本号或标识写回内存。
			//如果资源已被修改，操作失败，需要进行重试或者执行相应的冲突处理逻辑。
			"status":     JobStatusRunning,
			"version":    res.Version + 1,
			"updated_at": now,
		})
		if ans.Error != nil {
			return JobModel{}, err
		}
		if ans.RowsAffected == 0 {
			//没有抢到任务，没有修改
			continue
		}
		return res, nil

	}
}

func (g *gormJobDAO) UpdateChangeTime(ctx context.Context, id int64) error {
	now := time.Now()
	return g.db.WithContext(ctx).Model(&JobModel{}).Where("id =? AND status =? ", id, JobStatusRunning).Updates(map[string]any{"updated_at": now}).Error

}

func (g *gormJobDAO) GetJob(ctx context.Context, id int64) (JobModel, error) {
	var res JobModel
	//执行时间已经过期，说明正在等待被抢占
	err := g.db.WithContext(ctx).Where("id =?", id).First(&res).Error
	if err != nil {
		return JobModel{}, err
	}
	return res, nil
}
